/*
 * Copyright (c) 2020, China Mobile Communications Group Co.,Ltd.
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author       Notes
 * 2020-12-03     cmcc       the first version
 */

#include "httpUtil.h"

#include <stdio.h>
#include <os_stddef.h>
#include <string.h>
#include <webclient.h>
#include <sys/time.h>
#include <vfs_posix.h>

#include "ns_log.h"
#include "base64.h"
#include "UrlEncode.h"
#include "fileUtil.h"

static cJSON *handle_response_json(struct webclient_session *session, int callHandle);

cJSON *http_post_urlencode(char *url, char *key, char *content)
{
    //build content
    long content_length = strlen(content);
    long send_buf_size = content_length * 9 + 100; //url-encode and tag
    char *send_buf = os_malloc(send_buf_size);
    if (send_buf == OS_NULL)
    {
        LOG_EXT_E("fail to allocate mem:%d KB", send_buf_size / 1024);
        return OS_NULL;
    }

    int key_length = strlen(key);
    long pos = snprintf(send_buf, key_length + 2, "%s=", key);
    pos += URLEncode(content, content_length, send_buf + pos, send_buf_size - pos);

    //build http session
    struct webclient_session *session = webclient_session_create(PKG_NINESKY_HTTP_REQ_HEADER_BUF_SIZE);
    if (session == OS_NULL)
    {
        LOG_EXT_E("failed to create session");
        os_free(send_buf);
        return OS_NULL;
    }

    //build content type header
    webclient_header_fields_add(session, "Content-Type: %s\r\n", "application/x-www-form-urlencoded");
    webclient_header_fields_add(session, "Content-Length: %d\r\n", pos);

    //send post header
    int response_status = webclient_post(session, url, send_buf);
    if (response_status != 200)
    {
        LOG_EXT_E("failed to post");
        webclient_close(session);
        os_free(send_buf);
        return OS_NULL;
    }

    //free send buffer
    os_free(send_buf);

    return handle_response_json(session, 0);
}

cJSON *http_post_file_base64(char *url, char *fileKey, char *filePath)
{
    long contentSize = 0;

    //open file
    int fd = open(filePath, O_RDONLY);
    if (fd < 0)
    {
        LOG_EXT_D("can not open file: %s", filePath);
        return OS_NULL;
    }

    //seek file length
    contentSize = lseek(fd, 0, SEEK_END);
    if (contentSize <= 0)
    {
        LOG_EXT_D("can not seek file");
        close(fd);
        return OS_NULL;
    }

    //seek at beginning
    lseek(fd, 0, SEEK_SET);

    long SEND_BUF_SIZE = PKG_NINESKY_CONTENT_SEND_BUF_SIZE;
    long SEGMENT_SIZE = SEND_BUF_SIZE * 3 / 4; //base64

    //file content buffer
    char *contentBuffer = os_malloc(SEGMENT_SIZE);
    if (contentBuffer == OS_NULL)
    {
        LOG_EXT_E("failed to allocate mem:%d KB", SEGMENT_SIZE / 1024);
        return OS_NULL;
    }

    //build http session
    struct webclient_session *session = webclient_session_create(PKG_NINESKY_HTTP_REQ_HEADER_BUF_SIZE);
    if (session == OS_NULL)
    {
        LOG_EXT_E("failed to create session");
        os_free(contentBuffer);
        return OS_NULL;
    }

    //build content type header
    webclient_header_fields_add(session, "Content-Type: %s\r\n", "application/x-www-form-urlencoded");

    //build content length header
    char *tag_prefix = fileKey;
    int tag_length = strlen(tag_prefix) + 1; //"tag_prefix="

    long base64_content_length = (contentSize / 3) * 4;

    if (contentSize % 3 != 0)
    {
        base64_content_length += 4;
    }
    webclient_header_fields_add(session, "Content-Length: %d\r\n", (tag_length + base64_content_length));

    //send post header
    int response_status = webclient_post(session, url, OS_NULL);
    if (response_status != WEBCLIENT_OK)
    {
        LOG_EXT_E("failed to connect server");
        os_free(contentBuffer);
        webclient_close(session);
        return OS_NULL;
    }

    //send post body
    char *send_buf = os_malloc(SEND_BUF_SIZE + 100); //we may have some tags
    if (send_buf == OS_NULL)
    {
        LOG_EXT_E("failed to allocate mem:%d KB", SEND_BUF_SIZE / 1024);
        os_free(contentBuffer);
        webclient_close(session);
        return OS_NULL;
    }

    //add image tag
    strncpy(send_buf, tag_prefix, tag_length - 1);
    strncpy(send_buf + tag_length - 1, "=", 1);
    send_buf += tag_length;

    int first_send = 1; //mark the first segment including image tag
    long read_pos = 0, left = 0;

    while ((left = contentSize - read_pos) > 0)
    {
        long should_send = 0;
        if (left > SEGMENT_SIZE)
        {
            //encode SEGMENT_SIZE
            read(fd, contentBuffer, SEGMENT_SIZE);
            should_send = base64_encode(contentBuffer, SEGMENT_SIZE, send_buf);
        }
        else
        {
            //encode last part
            read(fd, contentBuffer, contentSize - read_pos);
            should_send = base64_encode(contentBuffer, contentSize - read_pos, send_buf);
        }

        if (first_send == 1)
        {
            send_buf -= tag_length; //move back
            should_send += tag_length;
            first_send = 0;
        }

        if (webclient_write(session, (const unsigned char *)(send_buf), should_send) != should_send)
        {
            LOG_EXT_E("webclient write failed");
            os_free(contentBuffer);
            os_free(send_buf);
            webclient_close(session);
            return OS_NULL;
        }

        read_pos += SEGMENT_SIZE;
    }

    //free buffer and fd
    os_free(send_buf);
    os_free(contentBuffer);
    close(fd);

    return handle_response_json(session, 1);
}

cJSON *http_post_content_base64(char *url, char *fileKey, char *contentBuffer, long contentSize)
{
    //build http session
    struct webclient_session *session = webclient_session_create(PKG_NINESKY_HTTP_REQ_HEADER_BUF_SIZE);
    if (session == OS_NULL)
    {
        LOG_EXT_E("failed to create session");
        return OS_NULL;
    }

    //build content type header
    webclient_header_fields_add(session, "Content-Type: %s\r\n", "application/x-www-form-urlencoded");

    //build content length header
    char *tag_prefix = fileKey;
    int tag_length = strlen(tag_prefix) + 1; //"tag_prefix="

    long base64_content_length = (contentSize / 3) * 4;
    LOG_EXT_D("base64_content_length=%ld", base64_content_length);
    if (contentSize % 3 != 0)
    {
        base64_content_length += 4;
    }
    webclient_header_fields_add(session, "Content-Length: %d\r\n", (tag_length + base64_content_length));

    //send post header
    int response_status = webclient_post(session, url, OS_NULL);
    if (response_status != WEBCLIENT_OK)
    {
        LOG_EXT_E("failed to connect server");
        webclient_close(session);
        return OS_NULL;
    }

    long SEND_BUF_SIZE = PKG_NINESKY_CONTENT_SEND_BUF_SIZE;
    long SEGMENT_SIZE = SEND_BUF_SIZE * 3 / 4; //base64

    //send post body
    char *send_buf = os_malloc(SEND_BUF_SIZE + 100); //we may have some tags
    if (send_buf == OS_NULL)
    {
        LOG_EXT_E("failed to allocate mem:%d KB", SEND_BUF_SIZE / 1024);
        webclient_close(session);
        return OS_NULL;
    }

    //add image tag
    strncpy(send_buf, tag_prefix, tag_length - 1);
    strncpy(send_buf + tag_length - 1, "=", 1);
    send_buf += tag_length;

    int first_send = 1; //mark the first segment including image tag
    long read_pos = 0, left = 0;

    while ((left = contentSize - read_pos) > 0)
    {
        long should_send = 0;
        if (left > SEGMENT_SIZE)
        {
            //encode SEGMENT_SIZE
            should_send = base64_encode(contentBuffer + read_pos, SEGMENT_SIZE, send_buf);
        }
        else
        {
            //encode last part
            should_send = base64_encode(contentBuffer + read_pos, contentSize - read_pos, send_buf);
        }

        if (first_send == 1)
        {
            send_buf -= tag_length; //move back
            should_send += tag_length;
            first_send = 0;
        }

        if (webclient_write(session, (const unsigned char *)(send_buf), should_send) != should_send)
        {
            LOG_EXT_E("webclient write failed");
            os_free(send_buf);
            webclient_close(session);
            return OS_NULL;
        }

        read_pos += SEGMENT_SIZE;
    }

    //free send buffer
    os_free(send_buf);

    return handle_response_json(session, 1);
}

cJSON *http_post_file_multipart_form(char *url, char *filePath, char **argName, char **argValue, int argCount)
{
    int exit_error = 0;
    long contentSize = 0;

    //open file
    int fd = open(filePath, O_RDONLY);
    if (fd < 0)
    {
        LOG_EXT_D("can not open file: %s", filePath);
        exit_error = -1;
        return OS_NULL;
    }

    //local vars
    long SEGMENT_SIZE = PKG_NINESKY_CONTENT_SEND_BUF_SIZE;
    char *contentBuffer = OS_NULL;
    struct webclient_session *session = OS_NULL;
    char post_bounder[50] = {0};
    long pos = 0;
    char *file_boundary = OS_NULL;
    long other_boundary_length = 0;
    char *other_boundary = OS_NULL;
    long content_length = 0;
    char content_type[100] = "multipart/form-data; boundary=";
    int response_status = 0;
    long read_pos = 0, left = 0;

    //seek file length
    contentSize = lseek(fd, 0, SEEK_END);
    if (contentSize <= 0)
    {
        LOG_EXT_D("can not seek file");
        exit_error = -1;
        goto __exit;
    }

    //seek at beginning
    lseek(fd, 0, SEEK_SET);

    //file content buffer
    contentBuffer = os_malloc(SEGMENT_SIZE);
    if (contentBuffer == OS_NULL)
    {
        LOG_EXT_E("failed to allocate mem:%d KB", SEGMENT_SIZE / 1024);
        exit_error = -1;
        goto __exit;
    }

    //build http session
    session = webclient_session_create(PKG_NINESKY_HTTP_REQ_HEADER_BUF_SIZE);
    if (session == OS_NULL)
    {
        LOG_EXT_E("failed to create session");
        exit_error = -1;
        goto __exit;
    }

    /**
     *  ---------------
     *      header
     *  ---------------
     *       \r\n
     *  ---------------    ----
     *      bounder          ^
     *    disposition        |
     *  ---------------      |
     *       \r\n            |
     *  ---------------      |
     *     content-1         v
     *  ---------------    ----
     *      bounder
     *    disposition
     *  ---------------
     *       \r\n
     *  ---------------
     *     content-1
     *  ---------------
     *    bounder-end
     */

    //build boundary
    struct timeval tv;
    gettimeofday(&tv, OS_NULL);
    sprintf(post_bounder, "----ninesky%ld", (long)tv.tv_sec);

    //file-boundary(first boundary)
    file_boundary = os_malloc(320);
    pos += snprintf(file_boundary, 50, "--%s\r\n", post_bounder);
    pos += snprintf(file_boundary + pos, 150, "Content-Disposition: form-data; name=\"%s\"; filename=\"%s\"\r\n", "image", get_file_name(filePath));
    pos += snprintf(file_boundary + pos, 100, "Content-Type: %s\r\n\r\n", "image/jpeg"); //only jpeg support

    //other-boundary and end-boundary
    for (int i = 0; i < argCount; i++)
    {
        other_boundary_length += 128; //http prefix format
        other_boundary_length += strlen(*(argName + i));
        other_boundary_length += strlen(*(argValue + i));
    }
    other_boundary_length += 50; //end-boundary

    other_boundary = os_malloc(other_boundary_length);
    pos = 0;
    for (int i = 0; i < argCount; i++)
    {
        pos += snprintf(other_boundary + pos, 50, "\r\n--%s\r\n", post_bounder);
        pos += snprintf(other_boundary + pos, 80, "Content-Disposition: form-data; name=\"%s\"\r\n\r\n", *(argName + i));
        pos += snprintf(other_boundary + pos, 20, "%s", *(argValue + i));
    }
    pos += snprintf(other_boundary + pos, 50, "\r\n--%s--\r\n", post_bounder); //end-boundary

    //build content length header
    content_length = strlen(file_boundary) + contentSize + pos;
    webclient_header_fields_add(session, "Content-Length: %d\r\n", content_length);

    //content-type
    strcat(content_type, post_bounder);
    webclient_header_fields_add(session, "Content-Type: %s\r\n", content_type);

    //send post header
    response_status = webclient_post(session, url, OS_NULL);
    if (response_status != WEBCLIENT_OK)
    {
        LOG_EXT_E("failed to connect server");
        exit_error = -1;
        goto __exit;
    }

    //send file boundary
    if (webclient_write(session, (const unsigned char *)(file_boundary), strlen(file_boundary)) != strlen(file_boundary))
    {
        LOG_EXT_E("failed to write");
        exit_error = -1;
        goto __exit;
    }

    //send file
    while ((left = contentSize - read_pos) > 0)
    {
        long should_send = 0;
        if (left > SEGMENT_SIZE)
        {
            //read SEGMENT_SIZE
            read(fd, contentBuffer, SEGMENT_SIZE);
            should_send = SEGMENT_SIZE;
        }
        else
        {
            //read last part
            read(fd, contentBuffer, contentSize - read_pos);
            should_send = contentSize - read_pos;
        }
        if (webclient_write(session, (const unsigned char *)(contentBuffer), should_send) != should_send)
        {
            LOG_EXT_E("fail to write");
            exit_error = -1;
            goto __exit;
        }

        read_pos += SEGMENT_SIZE;
    }

    //send other parts
    if (webclient_write(session, (const unsigned char *)(other_boundary), pos) != pos)
    {
        LOG_EXT_E("fail to write");
        exit_error = -1;
        goto __exit;
    }

    //clean off
__exit:
    if (fd >= 0)
    {
        close(fd);
    }
    if (contentBuffer != OS_NULL)
    {
        os_free(contentBuffer);
    }
    if (file_boundary != OS_NULL)
    {
        os_free(file_boundary);
    }
    if (other_boundary != OS_NULL)
    {
        os_free(other_boundary);
    }
    if (session != OS_NULL)
    {
        if (exit_error == -1)
        {
            webclient_close(session);
        }
        else
        {
            return handle_response_json(session, 1);
        }
    }

    return OS_NULL;
}

cJSON *http_post_content_multipart_form(char *url, char *contentBuffer, long contentSize,
                                        char *fileName, char **argName, char **argValue, int argCount)
{
    int exit_error = 0;

    long SEGMENT_SIZE = PKG_NINESKY_CONTENT_SEND_BUF_SIZE;

    //local vars
    char post_bounder[50] = {0};
    struct timeval tv;
    long pos = 0;
    char *file_boundary = OS_NULL;
    long other_boundary_length = 0;
    char *other_boundary = OS_NULL;
    long content_length = 0;
    char content_type[100] = "multipart/form-data; boundary=";
    int response_status = 0;
    long read_pos = 0, left = 0;

    //build http session
    struct webclient_session *session = webclient_session_create(PKG_NINESKY_HTTP_REQ_HEADER_BUF_SIZE);
    if (session == OS_NULL)
    {
        LOG_EXT_E("failed to create session");
        exit_error = -1;
        goto __exit;
    }

    //build boundary
    gettimeofday(&tv, OS_NULL);
    sprintf(post_bounder, "----ninesky%ld", (long)tv.tv_sec);

    //file-boundary(first boundary)
    file_boundary = os_malloc(320);
    pos += snprintf(file_boundary, 50, "--%s\r\n", post_bounder);
    pos += snprintf(file_boundary + pos, 150, "Content-Disposition: form-data; name=\"%s\"; filename=\"%s\"\r\n", "image", fileName);
    pos += snprintf(file_boundary + pos, 100, "Content-Type: %s\r\n\r\n", "image/jpeg"); //only jpeg support

    //other-boundary and end-boundary
    for (int i = 0; i < argCount; i++)
    {
        other_boundary_length += 128; //http prefix format
        other_boundary_length += strlen(*(argName + i));
        other_boundary_length += strlen(*(argValue + i));
    }
    other_boundary_length += 50; //end-boundary

    other_boundary = os_malloc(other_boundary_length);
    pos = 0;
    for (int i = 0; i < argCount; i++)
    {
        pos += snprintf(other_boundary + pos, 50, "\r\n--%s\r\n", post_bounder);
        pos += snprintf(other_boundary + pos, 80, "Content-Disposition: form-data; name=\"%s\"\r\n\r\n", *(argName + i));
        pos += snprintf(other_boundary + pos, 20, "%s", *(argValue + i));
    }
    pos += snprintf(other_boundary + pos, 50, "\r\n--%s--\r\n", post_bounder); //end-boundary

    //build content length header
    content_length = strlen(file_boundary) + contentSize + pos;
    webclient_header_fields_add(session, "Content-Length: %d\r\n", content_length);

    //content-type
    strcat(content_type, post_bounder);
    webclient_header_fields_add(session, "Content-Type: %s\r\n", content_type);

    //send post header
    response_status = webclient_post(session, url, OS_NULL);
    if (response_status != WEBCLIENT_OK)
    {
        LOG_EXT_E("failed to connect server");
        exit_error = -1;
        goto __exit;
    }

    //send file boundary
    if (webclient_write(session, (const unsigned char *)(file_boundary), strlen(file_boundary)) != strlen(file_boundary))
    {
        LOG_EXT_E("failed to write");
        exit_error = -1;
        goto __exit;
    }

    //send content
    while ((left = contentSize - read_pos) > 0)
    {
        long should_send = 0;
        if (left > SEGMENT_SIZE)
        {
            //read SEGMENT_SIZE
            should_send = SEGMENT_SIZE;
        }
        else
        {
            //read last part
            should_send = contentSize - read_pos;
        }
        if (webclient_write(session, (const unsigned char *)(contentBuffer + read_pos), should_send) != should_send)
        {
            LOG_EXT_E("fail to write");
            exit_error = -1;
            goto __exit;
        }

        read_pos += SEGMENT_SIZE;
    }

    //send other parts
    if (webclient_write(session, (const unsigned char *)(other_boundary), pos) != pos)
    {
        LOG_EXT_E("fail to write");
        exit_error = -1;
        goto __exit;
    }

    //clean off
__exit:
    if (file_boundary != OS_NULL)
    {
        os_free(file_boundary);
    }
    if (other_boundary != OS_NULL)
    {
        os_free(other_boundary);
    }
    if (session != OS_NULL)
    {
        if (exit_error == -1)
        {
            webclient_close(session);
        }
        else
        {
            return handle_response_json(session, 1);
        }
    }

    return OS_NULL;
}

static cJSON *handle_response_json(struct webclient_session *session, int callHandle)
{
    if (callHandle)
    {
        //response handling
        extern int webclient_handle_response(struct webclient_session * session);
        int response_status = webclient_handle_response(session);
        if (response_status != 200)
        {
            LOG_EXT_E("webclient POST response error: %d\n", response_status);
            return OS_NULL;
        }
    }

    int bytes_read = 0;
    unsigned char *response_buffer = (unsigned char *)web_malloc(PKG_NINESKY_HTTP_RSP_BUF_SIZE);
    memset(response_buffer, '\0', PKG_NINESKY_HTTP_RSP_BUF_SIZE);
    do
    {
        bytes_read = webclient_read(session, response_buffer, PKG_NINESKY_HTTP_RSP_BUF_SIZE);
        if (bytes_read <= 0)
        {
            break;
        }
    } while (1);

    LOG_EXT_D("=>%s\n", response_buffer);

    //load to json
    cJSON *json = cJSON_Parse((char *)response_buffer);
    if (json == NULL)
    {
        LOG_EXT_E("JSON ERROR:%s\n", cJSON_GetErrorPtr());
        return OS_NULL;
    }

    web_free(response_buffer);

    //close session
    webclient_close(session);

    return json;
}
